//! Main Shell interface that ties everything together

use crate::command::{Command, CommandBuilder};
use crate::commands::CommandRegistry;
use crate::config::Config;
use crate::error::{Result, ShellError};
use crate::executor::{ExecutionResult, Executor};
use std::path::{Path, PathBuf};
use std::sync::Arc;

/// Main Shell struct that provides the primary interface for R-XShell
#[derive(Debug)]
pub struct Shell {
    /// Configuration
    config: Arc<Config>,
    /// Command executor
    executor: Executor,
    /// Current working directory
    current_dir: PathBuf,
    /// Built-in command registry
    command_registry: Arc<CommandRegistry>,
}

impl Shell {
    /// Create a new Shell with default configuration
    pub fn new() -> Result<Self> {
        let config = Config::new();
        Self::with_config(config)
    }
    
    /// Create a new Shell with the given configuration
    pub fn with_config(config: Config) -> Result<Self> {
        config.validate()?;
        
        let executor = Executor::new()
            .with_default_timeout(config.default_timeout)
            .with_logging(config.logging.log_commands)
            .with_max_concurrent(config.execution.max_concurrent);
        
        let current_dir = config.default_working_dir
            .clone()
            .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
        
        Ok(Self {
            config: Arc::new(config),
            executor,
            current_dir,
            command_registry: Arc::new(CommandRegistry::new()),
        })
    }
    
    /// Load Shell from configuration file
    pub fn from_config_file<P: AsRef<Path>>(path: P) -> Result<Self> {
        let config = Config::load_from_file(path)?;
        Self::with_config(config)
    }
    
    /// Load Shell from environment variables
    pub fn from_env() -> Result<Self> {
        let config = Config::load_from_env();
        Self::with_config(config)
    }
    
    /// Get the current configuration
    pub fn config(&self) -> &Config {
        &self.config
    }
    
    /// Get the current working directory
    pub fn current_dir(&self) -> &Path {
        &self.current_dir
    }
    
    /// Change the current working directory
    pub fn cd<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
        let new_path = if path.as_ref().is_absolute() {
            path.as_ref().to_path_buf()
        } else {
            self.current_dir.join(path.as_ref())
        };
        
        if !new_path.exists() {
            return Err(ShellError::invalid_command(
                format!("Directory does not exist: {}", new_path.display())
            ));
        }
        
        if !new_path.is_dir() {
            return Err(ShellError::invalid_command(
                format!("Path is not a directory: {}", new_path.display())
            ));
        }
        
        self.current_dir = new_path.canonicalize()
            .map_err(|e| ShellError::Io(e))?;
        
        Ok(())
    }
    
    /// Create a new command builder
    pub fn cmd<S: Into<String>>(&self, program: S) -> CommandBuilder {
        CommandBuilder::new(program)
            .current_dir(self.current_dir.clone())
    }
    
    /// Execute a command asynchronously
    pub async fn run(&self, command: &Command) -> Result<ExecutionResult> {
        self.validate_command(command)?;
        
        // Check if this is a built-in command first
        if let Some(result) = self.command_registry.execute(self, &command.program, command.args.clone()).await {
            let start_time = std::time::Instant::now();
            match result {
                Ok(output) => {
                    let duration = start_time.elapsed();
                    return Ok(ExecutionResult::new(Some(0), output, String::new(), duration));
                }
                Err(e) => {
                    let duration = start_time.elapsed();
                    return Ok(ExecutionResult::new(Some(1), String::new(), e.to_string(), duration));
                }
            }
        }
        
        let mut cmd = command.clone();
        
        // Set working directory if not already set
        if cmd.working_dir.is_none() {
            cmd.working_dir = Some(self.current_dir.clone());
        }
        
        // Add default environment variables
        for (key, value) in &self.config.default_env {
            if !cmd.env.contains_key(key) {
                cmd.env.insert(key.clone(), value.clone());
            }
        }
        
        self.executor.execute(&cmd).await
    }
    
    /// Execute a command synchronously (blocking)
    pub fn run_blocking(&self, command: &Command) -> Result<ExecutionResult> {
        self.validate_command(command)?;
        
        let mut cmd = command.clone();
        
        // Set working directory if not already set
        if cmd.working_dir.is_none() {
            cmd.working_dir = Some(self.current_dir.clone());
        }
        
        // Add default environment variables
        for (key, value) in &self.config.default_env {
            if !cmd.env.contains_key(key) {
                cmd.env.insert(key.clone(), value.clone());
            }
        }
        
        self.executor.execute_blocking(&cmd)
    }
    
    /// Execute multiple commands concurrently
    pub async fn run_concurrent(&self, commands: Vec<Command>) -> Vec<Result<ExecutionResult>> {
        let validated_commands: Result<Vec<_>> = commands
            .into_iter()
            .map(|mut cmd| {
                self.validate_command(&cmd)?;
                
                // Set working directory if not already set
                if cmd.working_dir.is_none() {
                    cmd.working_dir = Some(self.current_dir.clone());
                }
                
                // Add default environment variables
                for (key, value) in &self.config.default_env {
                    if !cmd.env.contains_key(key) {
                        cmd.env.insert(key.clone(), value.clone());
                    }
                }
                
                Ok(cmd)
            })
            .collect();
        
        match validated_commands {
            Ok(cmds) => self.executor.execute_concurrent(cmds).await,
            Err(e) => vec![Err(e)],
        }
    }
    
    /// Get the command registry
    pub fn command_registry(&self) -> &CommandRegistry {
        &self.command_registry
    }
    
    /// List all available built-in commands
    pub fn list_builtin_commands(&self) -> Vec<&str> {
        self.command_registry.list_commands()
    }
    
    /// Validate a command against security policies
    fn validate_command(&self, command: &Command) -> Result<()> {
        // Check command length
        let command_line = command.command_line();
        if command_line.len() > self.config.security.max_command_length {
            return Err(ShellError::invalid_command(
                format!("Command too long: {} characters (max: {})", 
                    command_line.len(), 
                    self.config.security.max_command_length
                )
            ));
        }
        
        // Check forbidden commands
        for forbidden in &self.config.security.forbidden_commands {
            if command_line.contains(forbidden) {
                return Err(ShellError::invalid_command(
                    format!("Forbidden command detected: {}", forbidden)
                ));
            }
        }
        
        // Check allowed commands (if list is not empty)
        if !self.config.security.allowed_commands.is_empty() {
            let is_allowed = self.config.security.allowed_commands
                .iter()
                .any(|allowed| command.program.contains(allowed));
            
            if !is_allowed {
                return Err(ShellError::invalid_command(
                    format!("Command not in allowed list: {}", command.program)
                ));
            }
        }
        
        // Check for shell injection if not allowed
        if !self.config.security.allow_shell_injection {
            let dangerous_chars = ['|', '&', ';', '`', '$', '(', ')', '<', '>', '\n'];
            if command_line.chars().any(|c| dangerous_chars.contains(&c)) {
                return Err(ShellError::invalid_command(
                    "Potential shell injection detected"
                ));
            }
        }
        
        Ok(())
    }
}

impl Default for Shell {
    fn default() -> Self {
        Self::new().expect("Failed to create default shell")
    }
}

/// Convenience functions for quick shell operations
pub mod quick {
    use super::*;
    
    /// Execute a simple command and return the result
    pub async fn run<S: Into<String>>(program: S) -> Result<ExecutionResult> {
        let shell = Shell::new()?;
        let command = Command::new(program);
        shell.run(&command).await
    }
    
    /// Execute a command with arguments
    pub async fn run_with_args<S, I, A>(program: S, args: I) -> Result<ExecutionResult>
    where
        S: Into<String>,
        I: IntoIterator<Item = A>,
        A: Into<String>,
    {
        let shell = Shell::new()?;
        let command = Command::new(program).args(args);
        shell.run(&command).await
    }
    
    /// Execute a command and return only stdout
    pub async fn output<S: Into<String>>(program: S) -> Result<String> {
        let result = run(program).await?;
        if result.is_success() {
            Ok(result.stdout_trimmed().to_string())
        } else {
            Err(ShellError::command_failed(result.stderr_trimmed()))
        }
    }
    
    /// Execute a command in a specific directory
    pub async fn run_in_dir<S, P>(program: S, dir: P) -> Result<ExecutionResult>
    where
        S: Into<String>,
        P: Into<PathBuf>,
    {
        let shell = Shell::new()?;
        let command = Command::new(program).current_dir(dir);
        shell.run(&command).await
    }
    
    /// Check if a command exists in PATH
    pub async fn which<S: Into<String>>(program: S) -> Result<PathBuf> {
        let program_name = program.into();
        
        #[cfg(windows)]
        let which_cmd = format!("where {}", program_name);
        #[cfg(not(windows))]
        let which_cmd = format!("which {}", program_name);
        
        let result = output(which_cmd).await?;
        let path = result.lines().next()
            .ok_or_else(|| ShellError::invalid_command(
                format!("Command not found: {}", program_name)
            ))?
            .to_string();
        
        Ok(PathBuf::from(path))
    }
    
    /// Get current working directory
    pub fn pwd() -> Result<PathBuf> {
        std::env::current_dir().map_err(ShellError::Io)
    }
    
    /// List directory contents
    pub async fn ls<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
        let path_str = path.as_ref().to_string_lossy().to_string();
        
        #[cfg(windows)]
        let ls_cmd = format!("dir /b \"{}\"", path_str);
        #[cfg(not(windows))]
        let ls_cmd = format!("ls -1 \"{}\"", path_str);
        
        let result = output(ls_cmd).await?;
        Ok(result.lines().map(|s| s.to_string()).collect())
    }
}